<?php //$Rev:: 58         $ // DO NOT REMOVE

/*   PLEASE READ LICENSE BEFORE USING
 * +-----------------------------------------------------------------------------+
 * | SVN Information:                                                            |
 * +-----------------------------------------------------------------------------+
 * $Id: ATAC.php 58 2010-06-16 22:44:17Z andrew.heebner $
 */

// Deny script timeout and enable error reporting
set_time_limit(0);
error_reporting(E_ALL);

// Require misc files
require_once 'misc/Constants.php';

// Require class files
if (is_dir('classes/')) {
    if ($files = glob('classes/cls_*.php')) {
        foreach ($files as $f) require_once $f;
    }
} else {
    die('Unable to find classes directory');
}

// Require callback files
if (is_dir('callbacks/')) {
    if ($files = glob('callbacks/*.php')) {
        foreach ($files as $f) require_once $f;
    }
} else {
    die('Unable to find callback directory');
}

// initialize the DB
Db::setConnectionInfo('db/atac.sqlite', null, null, 'sqlite');

// Buffer output to console
ob_implicit_flush();

/**
 * ATAC provides a customizable wrapper to the TOC protocol.
 * 
 * @author Andrew Heebner <andrew@evilwalrus.com> 
 * @version $Revision: 58 $
 * @require PHP 5.0
 */
class ATAC
{

    // User vars (modifiable through constructor)
    public $user = array(
        'screenname'    =>  null,
        'password'      =>  null,
        'debug'         =>  false,
        'defensive'     =>  false,
        'chats'         =>  array(),
        'admins'        =>  array(),
        'admin'         =>  false,
        'ident'         =>  false,
        'modules'       =>  'modules',
        'modules_help'  =>  'modules/help/',
        'callbacks'     =>  'callbacks/',
        'data'          =>  'data/',
        'msgalert'      =>  '!',
        'use_icq'       =>  false,
        'throttle_tl'   =>  2,
        'connection'    =>  array('tries'   =>  5,
                                  'sleep'   =>  3,
                                  'timeout' =>  10),
        'evil'          =>  0,
        'uuid'          =>  '',
        'helpcmd'       =>  '?'
    );

    // Connectivity variables
    public $conn = array(
        'auth_serv'     =>  array('aim' =>  'login.oscar.aol.com',
                                  'icq' =>  'login.icq.com'),
        'auth_port'     =>  5190,
        'toc_serv'      =>  'toc.oscar.aol.com',
        'toc_port'      =>  9898,
        'protocol'      =>  'TOC2.0',
        'language'      =>  'english-US'
    );

    // Away message variables
    public $away = array(
        'isaway'    =>  false,
        'message'   =>  null
    );

    // Connection variables
	public $resource;

    // Add-on class variable references
	public $utilities;
    public $module;
	public $chat;
	public $parser;
	public $buddy;
    public $errorhandler;
    public $sflap;
    public $throttle;

	// User handler array
	public $handlers = array();

    // ignore logging these
    public $ignorecmd = array();

    // budylist holder
    public $buddylist = array();


	/**
	 * __construct: ATAC Constructor
	 * 
	 * @access public 
	 * @return void 
	 * @throws ATACException
	 */
	public function __construct($options, $auto = true)
	{
        // ensure we're using the right PHP version
        $this->_version();

        // do some config checking
        if (!is_array($options) && is_file($options)) {
            // get the file extension
            $fe = pathinfo($options, PATHINFO_EXTENSION);
            switch (strtolower($fe)) {
                case 'json':
                    if (null === ($_f = @json_decode(file_get_contents($options), true))) {
                        throw new ATACException('JSON configuration file failed to decode.  Please check your syntax.');
                    }
                    $options = $_f['ATAC_CONFIG'];
                    break;
                case 'ini':
                    if (false === ($_f = @parse_ini_file($options, true))) {
                        throw new ATACException('INI configuration file failed to parse.  Please check your syntax.');
                    }
                    $options = $_f['ATAC_CONFIG'];
                    break;
            }
        }
        
        // continue parsing info
        foreach ($options as $k => $v) {
            switch ($k) {
                case 'screenname':
                    $this->user[$k] = $this->aim_normalize($v);
                    break;
                case 'admins':
                    if (!is_array($k)) {
                        @settype($this->user['admins'], 'string');
                    } elseif(is_array($k)) {
                        if (count($k) == 0) {
                            throw new ATACException('At least one administrator is required for ATAC to run properly.');
                        } elseif(count($k) == 1) {
                            @settype($this->user['admins'], 'string');
                        }
                    }
                    $this->user[$k] = $v;
                    break;
                default:
                    $this->user[$k] = $v;
                    break;
            }
        }
 
        if ($auto === true) {
            // Attempt to connect and login
	    	try {
                // Create object references
                $this->utilities    = new Utilities($this);
                $this->chat         = new Chat($this);
                $this->parser       = new Parser($this);
                $this->buddy        = new Buddy($this);
                $this->errorhandler = new ErrorHandler($this);
                $this->sflap        = new SFLAP($this);
                $this->throttle     = new Throttle($this);

                // check if we were run yet
                $fr = Db::getValue('SELECT firstrun FROM atac_stats');
                if ($fr == 0 || is_null($fr)) {
                    // set the firstrun flag
                    Db::execute('UPDATE atac_stats SET firstrun = ?', time());
                }

                // generate a unique uuid for this installation
                $this->user['uuid'] = Db::getValue('SELECT guid FROM atac_stats');
                if ($this->user['uuid'] == 0 || is_null($this->user['uuid'])) {
                    // set the firstrun flag
                    $uuid = $this->utilities->aim_generate_uuid();
                    Db::execute('UPDATE atac_stats SET guid = ?', $uuid);
                    $this->user['uuid'] = $uuid;
                }

                // finally, connect to the server
                $this->aim_connect();

                // we load this one *after* we sign on
                $this->module       = new ATACModule($this);

    		} 
	    	catch (ATACException $ex) {
		    	$ex->display();
    		}
        }
	}

    public static function Factory($config, $auto = true)
    {
        try {
            $inst = new ATAC($config);
            if ($inst instanceof ATAC) {
                return $inst;
            }
        } catch (ATACException $ex) {
            $ex->display();
        }
    }

	/**
	 * __destruct: ATAC Destructor
	 * 
	 * @access public 
	 * @return void 
	 */
	public function __destruct()
	{
		unset($this->resource, $this->chat, $this->utilities,
              $this->buddy, $this->parser, $this->errorhandler,
              $this->module, $this->sflap, $this->throttle);
	} 

	/**
	 * __call: ATAC invalid method parser
	 * 
	 * @param string $method Invalid method called
	 * @access private 
	 * @return void 
	 * @throws ATACException
	 */
	public function __call($method, $args)
	{
		throw new ATACException('Invalid method called => (' . __CLASS__ . '::' . $method . ')');
	}
    
	/**
	 * _version: Run compatibility check (against PHP5)
	 * 
	 * @param float $v (optional) Version to compare against
	 * @access private 
	 * @return boolean 
	 * @throws ATACException
	 * @proto boolean _version([integer version])
	 */
	private function _version($v = '5.3.0')
	{
        if (version_compare(PHP_VERSION, $v, '>=')) {
			return true;
		} else {
			throw new ATACException('PHP version ' . $v . ' required; have version ' . PHP_VERSION);
		}
        return false;
	}

	public function apiVersion()
	{
		return 'Testing';
    }

	/**
	 * aim_debug: Print debug text to console
	 * 
	 * @param string $text Text to print text
	 * @param string $type (optional) Message type
	 * @access public 
	 * @return void 
	 * @proto void aim_debug(string text_to_print [, string message_type])
	 */
	public function aim_debug($text, $type = AIM_INFO)
	{
		$_dl = '[' . $type . ' - ' . date('m/d/y @ h:i:sA', time()) . ']:  ' . $text . "\n";
		if ($type == AIM_ERR) {
            die($_dl);
        } else {
            echo $_dl;
        }
    }

	/**
	 * aim_connect: Connect to the TOC server
	 * 
	 * @access private 
	 * @return boolean 
	 * @throws ATACException
	 * @proto boolean aim_connect(void)
	 */
	private function aim_connect()
	{
        @readfile($this->user['data'] . 'atac.asc');
        if ($this->resource = Connection::init($this->conn['toc_serv'], $this->conn['toc_port'])) {
            $this->aim_login();
			return true;
        } else {
            throw new ATACException('Could not make connection to TOC server');
        }
        return false;
	} 

	/**
	 * aim_disconnect: Disconnect from the TOC server
	 * 
	 * @access public 
	 * @return boolean 
	 * @throws ATACException
	 * @proto boolean aim_connect(void)
	 */
	public function aim_disconnect()
	{
		if ($this->aim_connected() === true) {
			fclose($this->resource);
            return true;
		} else {
			throw new ATACException('No active connection was found to disconnect');
		}
        return false;
	} 

	/**
	 * aim_connected: Check if connection is still alive
	 * 
	 * @access public 
	 * @return boolean 
	 * @throws ATACException
	 * @proto boolean aim_connected(void)
	 */
	public function aim_connected()
	{
		if (is_resource($this->resource)) {
			return true;
		} else {
			throw new ATACException('Unable to find active connection');
		}
        return false;
	} 

	/**
	 * aim_login: Send login information to TOC server
	 * 
	 * @access private 
	 * @return void 
	 * @throws ATACException
	 * @proto void aim_login(void)
	 */
	private function aim_login()
	{
        $this->sflap->aim_sflap_raw(SFLAP_FLAP_SIGNONSTR);
        $result = $this->sflap->aim_sflap_recv();

        if ($result['frameType'] != SFLAP_TYPE_SIGNON) {
            $this->sflap->aim_sflap_signon();
            $this->sflap->aim_sflap_toc_signon();
            sleep(1);

    		try {
	    		if ($this->aim_connected()) {
		    		$this->sflap->aim_sflap_recv();
                    $this->user['info'] = 'Powered by <a href="http://atac.evilwalr.us">ATAC</a> - (PHP ' . phpversion() . '/' . PHP_OS . ')<br>';
				    $this->utilities->aim_set_info($this->user['info']);
    				$this->buddy->aim_add_buddy($this->user['screenname']);
	    			$this->sflap->aim_sflap_send(SFLAP_TYPE_DATA, 'toc_init_done');
                    $this->buddy->aim_list_mode(LIST_PERMIT_ALL);
                    $this->sflap->aim_sflap_send(SFLAP_TYPE_DATA, 'toc_set_caps ' . CAPS_FILE_SEND_UID . ' ' . CAPS_BUDDY_ICON_UID);
                    Db::execute('UPDATE atac_stats SET signon = ?', time());
                    // save screenname
                    Db::execute('UPDATE atac_stats SET screenname = ?', $this->user['screenname']);
	    		} 
		    } catch (ATACException $ex) {
			    $ex->display();
    		} 
        } else {
            throw new ATACException('Unable to reply to server frame');
        }
	} 

	/**
	 * aim_loop: Run main TOC loop for connection
	 * 
	 * @access public 
	 * @return void 
	 * @proto void aim_loop(void)
	 */
	public function aim_loop()
	{
        while ($this->aim_connected()) {
            echo $this->sflap->aim_sflap_recv();
        }
    }

	/**
	 * aim_roast_pass: 'Roasts' password with AOL-type encryption
	 * 
	 * @param string $pass Password to roast for AOL-crypt
	 * @access public 
	 * @return string 
	 * @proto string aim_roast_pass(string pass_to_encrypt)
	 */
	public function aim_roast_pass($pass)
	{
        static $roast = 'Tic/Toc';
        static $hex = '0x';
    
        for ($i = 0; $i < strlen($pass); $i++) {
            $hex.= bin2hex($pass[$i] ^ $roast[($i % 7)]);
        }
        
        return $hex;
	}

	/**
	 * aim_toc2_hash: Encodes user/pass for TOC2 signon
	 *
     * @param string $user Client username
	 * @param string $pass Client password
	 * @access public
	 * @return integer
	 * @proto integer aim_toc2_hash(string username, string password)
	 */    
    public function aim_toc2_hash($user, $pass)
    {
        $sn = ord($user{0}) - 96;
        $pw = ord($pass{0}) - 96;
        $a = $sn * 7696 + 738816;
        $b = $sn * 746512;
        return ($pw * $a) - $a + $b + 71665152;
    }

	/**
	 * aim_normalize: Normalize data
	 * 
	 * @param string $data Data to normalize
	 * @access public 
	 * @return string 
	 * @proto string aim_normalize(string text_to_normalize)
	 */
	public function aim_normalize($data)
	{
		return strtolower(preg_replace("/[^A-Za-z0-9]/", '', $data));
	} 

	/**
	 * aim_encode: Special encoding for text to send to TOC server
	 * 
	 * @param string $data Data to encode
	 * @access public 
	 * @return string 
	 * @proto string aim_encode(string text_to_encode)
	 */
	public function aim_encode($data)
	{
		return '"' . preg_replace("/([\\\}\{\(\)\[\]\$\"])/", "\\\\\\1", $data) . '"';
	} 

} // End ATAC

?>